//
//  STDSPuzzleUnitEffect.hpp
//  STDSRenderSDKIOS
//
//  Created by 陈学明 on 2023/4/17.
//

#ifndef STDSPuzzleUnitEffect_hpp
#define STDSPuzzleUnitEffect_hpp

#include "STDSBaseEffect.hpp"
#include <glm/glm.hpp>
#include <glm/ext/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
#include <glm/ext/scalar_constants.hpp> // glm::pi

using namespace glm;

enum STDSPuzzleMixType {
    STDSPuzzleMixTypeNone = 0,
    STDSPuzzleMixTypeNormal = 1,
    STDSPuzzleMixTypeStars = 2,
    STDSPuzzleMixTypeGrain = 3,
    STDSPuzzleMixTypeBreak = 4,
    STDSPuzzleMixTypeGlass = 5,
    STDSPuzzleMixTypeRainbow = 6,
    STDSPuzzleMixTypeCartoon = 7,
};

class STDSPuzzleUnitEffect: public STDSBaseEffect {
public:
    STDSPuzzleUnitEffect() {
        mEffectType = STDSEffectTypePuzzleUnit;
    }
    ~STDSPuzzleUnitEffect() {
//        printf("dealloc--STDSMosaicEffect\n");
    }
    
    void initGL() override {
        STDSBaseEffect::initGL();
    }
    
    void setFloatArguments(const char *name, float *floatValue, int n) override {
        if (strcmp("mix", name) == 0) {
            // Top-Left-Bottom-Right
            mMix = *floatValue;
        } else if (strcmp("scaleV", name) == 0) {
            mScale.y *= -1;
        } else if (strcmp("scaleH", name) == 0) {
            mScale.x *= -1;
        } else if (strcmp("rotate", name) == 0) {
            mAngle = mAngle + (*floatValue);
            mTranslate = vec2(0.0, 0.0);
            mScale = vec2(1.0, 1.0);
        } else if (strcmp("translate", name) == 0) {
            vec2 temp = vec2(*floatValue, *(floatValue+1));
            bool result = false;
            if (checkMove(temp.x, 0.0)) {
                mTranslate.x += temp.x;
                result = true;
            }
            if (checkMove(0.0, temp.y)) {
                mTranslate.y += temp.y;
                result = true;
            }
            mCheckResult = result;
        } else if (strcmp("scale", name) == 0) {
            float temp = (*floatValue);
            if (checkScale(temp)) {
                mScale += (*floatValue) - 1.0;
            }
        } else if (strcmp("resetTrans", name) == 0) {
            mScale = vec2(1.0, 1.0);
            mTranslate = vec2(0.0, 0.0);
            mAngle = 0.0;
            mCheckResult = true;
        } else if (strcmp("rect", name) == 0) {
            mRect.x = *floatValue;
            mRect.y = *(floatValue+1);
            mRect.z = *(floatValue+2);
            mRect.w = *(floatValue+3);
        }
    }
    
    void setIntArtuments(const char *name, int *intValue, int n) override {
        if (strcmp("type", name) == 0) {
            mType = STDSPuzzleMixType(*intValue);
            mContext->autoRelease(mPuzzleProgram);
            mPuzzleProgram = nullptr;
        }
    }
    
    bool checkStatus(const char *label) override {
        return mCheckResult;
    }
    
public:
    shared_ptr<STDSShaderProgram> getEffectProgram() {
        if (mPuzzleProgram == nullptr) {
            switch (mType) {
                case STDSPuzzleMixTypeNone:
                    mPuzzleProgram = mContext->getProgram(kShaderSRCPuzzleUnit);
                    break;
                case STDSPuzzleMixTypeNormal:
                    mPuzzleProgram = mContext->getProgram(kShaderSRCPuzzleUnit);
                    break;
                case STDSPuzzleMixTypeStars:
                    mPuzzleProgram = mContext->getProgram(kShaderSRCPuzzleUnit);
                    break;
                case STDSPuzzleMixTypeGlass:
                    mPuzzleProgram = mContext->getProgram(kShaderSRCPuzzleUnit);
                    break;
                case STDSPuzzleMixTypeGrain:
                    mPuzzleProgram = mContext->getProgram(kShaderSRCPuzzleGrain);
                    break;
                case STDSPuzzleMixTypeBreak:
                    mPuzzleProgram = mContext->getProgram(kShaderSRCPuzzleBreak);
                    break;
                case STDSPuzzleMixTypeCartoon:
                    mPuzzleProgram = mContext->getProgram(kShaderSRCPuzzleUnit);
                    break;
                case STDSPuzzleMixTypeRainbow:
                    mPuzzleProgram = mContext->getProgram(kShaderSRCPuzzleRainbow);
                    break;
                default:
                    break;
            }
        }
        return mPuzzleProgram;
    }
    
    void draw() override {

        auto program = getEffectProgram();
        if (program == nullptr) {
            return;
        }
        if (mTexture == nullptr) {
            return;
        }
//        drawTexture(mTexture->getId());
//        return;
        mTargetSize = vec2(mSizeSRC.x * mMix, mSizeSRC.y * mMix);
        float insetsH = (1.0 - mSizeSRC.x/mTargetSize.x);
        float insetsV = (1.0 - mSizeSRC.y/mTargetSize.y);
        
        auto canvaseSize = mContext->getSizeSRC();
        vec4 insetEdge(insetsH, insetsV, insetsH, insetsV);// left top right bottom
        // 靠近左边界
        if (mRect.x < 0.5) {
            insetEdge.x = 0;
        }
        // 靠近上边界
        if (mRect.y < 0.5) {
            insetEdge.y = 0;
        }
        // 靠近右边界
        if ((canvaseSize.x - mRect.x - mRect.z) < 0.5) {
            insetEdge.z = 0;
        }
        // 靠近下边界
        if ((canvaseSize.y - mRect.y - mRect.w) < 0.5) {
            insetEdge.w = 0;
        }
        
        float viewP = mTargetSize.y/mTargetSize.x;
        float composeP = float(mTexture->getHeight())/float(mTexture->getWidth());
        vec2 correctScale(1.0, 1.0);
        if (viewP < composeP) {
            correctScale.x = 1.0;
            correctScale.y = mTargetSize.y/(mTargetSize.x * composeP);
        } else {
            correctScale.x = mTargetSize.x/(mTargetSize.y / composeP);
            correctScale.y = 1.0;
        }
        
        program->use();
        updateTransformation();
        updateCavaseMat();
        updateTexMat(mAngle);
        mat4 texTransMat = mCorrectCavaseMat * mCorrectTexMat * mTransformationMat;
        
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture->getId());
        program->setUniform(program->getUniform("uTexture"), 0);
        program->setUniform4f(program->getUniform("insetsEdge"), insetEdge.x, insetEdge.y, insetEdge.z, insetEdge.w);
        program->setMatrix(program->getUniform("uTexTransMat"), glm::value_ptr(texTransMat));
        program->setMatrix(program->getUniform("uMatVex"), glm::value_ptr(mMatV));
        program->setMatrix(program->getUniform("uMatTex"), glm::value_ptr(mMatT));
        if (mType == STDSPuzzleMixTypeRainbow) {
            program->setUniform2f(program->getUniform("uSize"), mTexture->getWidth(), mTexture->getHeight());
        } else {
            program->setUniform2f(program->getUniform("uSize"), mSizeSRC.x, mSizeSRC.y);
        }
        
        //uSize
        mAtbRect.setPosVex(program->getAttrib("aPos"));
        mAtbRect.setPosTex(program->getAttrib("aTexCoord"));
        mAtbRect.draw();
        program->unuse();
    }
    
private:
    bool checkRotate(float &_angle) {
        updateCavaseMat();
        updateTexMat(_angle);
        return checkTransformation(mTransformationMat);
    }
    
    bool checkMove(float _dx, float _dy) {
        // 移动矩阵
        mat4 moveMat(1.0);
        moveMat = glm::translate(moveMat, vec3(0.5, 0.5, 0.0));
        moveMat = glm::scale(moveMat, vec3(1.0/mSizeSRC.x, 1.0/mSizeSRC.y, 1.0));
        moveMat = glm::translate(moveMat, vec3(-_dx, -_dy, 0.0));
        moveMat = glm::scale(moveMat, vec3(mSizeSRC.x, mSizeSRC.y, 1.0));
        moveMat = glm::translate(moveMat, vec3(-0.5, -0.5, 0.0));
        
        updateCavaseMat();
        updateTexMat(mAngle);
        mat4 tempTransMat = moveMat * mTransformationMat;
        auto result = checkTransformation(tempTransMat);
        return result;
    }
    
    bool checkScale(float &_scale) {
        mat4 scaleMat(1.0);
        scaleMat = glm::translate(scaleMat, vec3(0.5, 0.5, 0.0));
        scaleMat = glm::scale(scaleMat, vec3(1.0/mSizeSRC.x, 1.0/mSizeSRC.y, 1.0));
        scaleMat = glm::scale(scaleMat, vec3(1.0/_scale, 1.0/_scale, 1.0));
        scaleMat = glm::scale(scaleMat, vec3(mSizeSRC.x, mSizeSRC.y, 1.0));
        scaleMat = glm::translate(scaleMat, vec3(-0.5, -0.5, 0.0));
        
        updateCavaseMat();
        updateTexMat(mAngle);
        mat4 tempTransMat = scaleMat * mTransformationMat;
        auto result = checkTransformation(tempTransMat);
        return checkTransformation(tempTransMat);
    }
    
    bool checkTransformation(mat4 &_transMat) {
        mat4 transMat = mCorrectCavaseMat * mCorrectTexMat * _transMat;
        vec4 A(0.0, 0.0, 0.0, 1.0);
        vec4 B(1.0, 0.0, 0.0, 1.0);
        vec4 C(1.0, 1.0, 0.0, 1.0);
        vec4 D(0.0, 1.0, 0.0, 1.0);
        
        A = transMat * A;
        B = transMat * B;
        C = transMat * C;
        D = transMat * D;
        
        vector<vec4> vexs;
        vexs.push_back(A);
        vexs.push_back(B);
        vexs.push_back(C);
        vexs.push_back(D);
        
        float maxX = 0.0, maxY = 0.0, minX = 10000.0, minY = 10000.0;
        for (vec4 &vex:vexs) {
            if (vex.x > maxX) {
                maxX = vex.x;
            }
            if (vex.y > maxY) {
                maxY = vex.y;
            }
            if (vex.x < minX) {
                minX = vex.x;
            }
            if (vex.y < minY)  {
                minY = vex.y;
            }
        }
        
        if (minX < 0.0 || minY < 0.0 || maxX > 1.0 || maxY > 1.0) {
            mCheckResult = false;
            return mCheckResult;
        }
        mCheckResult = true;
        return mCheckResult;
    }
    
    void updateTransformation() {
        mTransformationMat = mat4(1.0);
        mTransformationMat = glm::translate(mTransformationMat, vec3(0.5, 0.5, 0.0));
        mTransformationMat = glm::scale(mTransformationMat, vec3(1.0/mSizeSRC.x, 1.0/mSizeSRC.y, 1.0));
        
        mTransformationMat = glm::scale(mTransformationMat, vec3(1.0/mScale.x, 1.0/mScale.y, 1.0));
        mTransformationMat = glm::translate(mTransformationMat, vec3(-mTranslate.x, -mTranslate.y, 0.0));
        
        mTransformationMat = glm::scale(mTransformationMat, vec3(mSizeSRC.x, mSizeSRC.y, 1.0));
        mTransformationMat = glm::translate(mTransformationMat, vec3(-0.5, -0.5, 0.0));
    }
    
    void updateTexMat(float &_angle) {
        if (mTexture == nullptr) {
            return;
        }
        int tempAngle  = ((int)_angle % 180) - 1;
        float arc = (_angle/180)*M_PI;
        float scale = 1.0;
        if (tempAngle > 0 && float(mTexture->getWidth())/float(mTexture->getHeight()) > 1.0) {
            scale = float(mTexture->getWidth())/float(mTexture->getHeight()) + 0.1; // 0.1为容错处理，可以加可以不加，加上效果会好一点
        }
        
        mCorrectTexMat = mat4(1.0);
        mCorrectTexMat = glm::translate(mCorrectTexMat, vec3(0.5, 0.5, 0.0));
        mCorrectTexMat = glm::scale(mCorrectTexMat, vec3(1.0/mSizeSRC.x, 1.0/mSizeSRC.y, 1.0));

        mCorrectTexMat = glm::rotate(mCorrectTexMat, arc, vec3(0.0, 0.0, 1.0));
        mCorrectTexMat = glm::scale(mCorrectTexMat, vec3(1.0/scale, 1.0/scale, 1.0));
        
        mCorrectTexMat = glm::scale(mCorrectTexMat, vec3(mSizeSRC.x, mSizeSRC.y, 1.0));
        mCorrectTexMat = glm::translate(mCorrectTexMat, vec3(-0.5, -0.5, 0.0));
    }
    
    void updateCavaseMat() {
        if (mTexture == nullptr) {
            return;
        }
        mTargetSize = vec2(mSizeSRC.x * mMix, mSizeSRC.y * mMix);
        float viewP = mTargetSize.y/mTargetSize.x;
        float composeP = float(mTexture->getHeight())/float(mTexture->getWidth());
        vec2 correctScale(1.0, 1.0);
        if (viewP < composeP) {
            correctScale.x = 1.0;
            correctScale.y = mSizeSRC.y/(mSizeSRC.x * composeP);
        } else {
            correctScale.x = mSizeSRC.x/(mSizeSRC.y / composeP);
            correctScale.y = 1.0;
        }
        
        mCorrectCavaseMat = mat4(1.0);
        mCorrectCavaseMat = glm::translate(mCorrectCavaseMat, vec3(0.5, 0.5, 0.0));
        mCorrectCavaseMat = glm::scale(mCorrectCavaseMat, vec3(correctScale.x, correctScale.y, 1.0));
        mCorrectCavaseMat = glm::translate(mCorrectCavaseMat, vec3(-0.5, -0.5, 0.0));
    }

private:
    mat4 mCorrectCavaseMat{1.0}; // 矫正画布矩阵
    mat4 mCorrectTexMat{1.0}; // 矫正纹理矩阵
    mat4 mTransformationMat{1.0}; // 仿射变换矩阵
    STDSPuzzleMixType mType{STDSPuzzleMixTypeNormal};// 0:  无样式，1：基础，2:美式颗粒，3：故障，4：毛玻璃，5：彩虹
    float mMix{1.0};
    vec2 mTargetSize{0.0, 0.0};
    bool mCheckResult{true};
    vec2 mScale{1.0, 1.0};
    float mAngle{0.0};
    vec2 mTranslate{0.0, 0.0};
    vec4 mRect{0.0,0.0,0.0,0.0};
    string mNoisePath;
    shared_ptr<STDSShaderProgram> mPuzzleProgram{nullptr};
};


#endif /* STDSPuzzleUnitEffect_hpp */
